// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // This file exists to aggregate all of the javascript used by the // settings page into a single file which will be flattened and served // as a single resource. // Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. cr.define('options', function() { ///////////////////////////////////////////////////////////////////////////// // Preferences class: /** * Preferences class manages access to Chrome profile preferences. * @constructor */ function Preferences() { // Map of registered preferences. this.registeredPreferences_ = {}; } cr.addSingletonGetter(Preferences); /** * Sets a Boolean preference and signals its new value. * @param {string} name Preference name. * @param {boolean} value New preference value. * @param {boolean} commit Whether to commit the change to Chrome. * @param {string} metric User metrics identifier. */ Preferences.setBooleanPref = function(name, value, commit, metric) { if (!commit) { Preferences.getInstance().setPrefNoCommit_(name, 'bool', Boolean(value)); return; } var argumentList = [name, Boolean(value)]; if (metric != undefined) argumentList.push(metric); chrome.send('setBooleanPref', argumentList); }; /** * Sets an integer preference and signals its new value. * @param {string} name Preference name. * @param {number} value New preference value. * @param {boolean} commit Whether to commit the change to Chrome. * @param {string} metric User metrics identifier. */ Preferences.setIntegerPref = function(name, value, commit, metric) { if (!commit) { Preferences.getInstance().setPrefNoCommit_(name, 'int', Number(value)); return; } var argumentList = [name, Number(value)]; if (metric != undefined) argumentList.push(metric); chrome.send('setIntegerPref', argumentList); }; /** * Sets a double-valued preference and signals its new value. * @param {string} name Preference name. * @param {number} value New preference value. * @param {boolean} commit Whether to commit the change to Chrome. * @param {string} metric User metrics identifier. */ Preferences.setDoublePref = function(name, value, commit, metric) { if (!commit) { Preferences.getInstance().setPrefNoCommit_(name, 'double', Number(value)); return; } var argumentList = [name, Number(value)]; if (metric != undefined) argumentList.push(metric); chrome.send('setDoublePref', argumentList); }; /** * Sets a string preference and signals its new value. * @param {string} name Preference name. * @param {string} value New preference value. * @param {boolean} commit Whether to commit the change to Chrome. * @param {string} metric User metrics identifier. */ Preferences.setStringPref = function(name, value, commit, metric) { if (!commit) { Preferences.getInstance().setPrefNoCommit_(name, 'string', String(value)); return; } var argumentList = [name, String(value)]; if (metric != undefined) argumentList.push(metric); chrome.send('setStringPref', argumentList); }; /** * Sets a string preference that represents a URL and signals its new value. * The value will be fixed to be a valid URL when it gets committed to Chrome. * @param {string} name Preference name. * @param {string} value New preference value. * @param {boolean} commit Whether to commit the change to Chrome. * @param {string} metric User metrics identifier. */ Preferences.setURLPref = function(name, value, commit, metric) { if (!commit) { Preferences.getInstance().setPrefNoCommit_(name, 'url', String(value)); return; } var argumentList = [name, String(value)]; if (metric != undefined) argumentList.push(metric); chrome.send('setURLPref', argumentList); }; /** * Sets a JSON list preference and signals its new value. * @param {string} name Preference name. * @param {Array} value New preference value. * @param {boolean} commit Whether to commit the change to Chrome. * @param {string} metric User metrics identifier. */ Preferences.setListPref = function(name, value, commit, metric) { if (!commit) { Preferences.getInstance().setPrefNoCommit_(name, 'list', value); return; } var argumentList = [name, JSON.stringify(value)]; if (metric != undefined) argumentList.push(metric); chrome.send('setListPref', argumentList); }; /** * Clears the user setting for a preference and signals its new effective * value. * @param {string} name Preference name. * @param {boolean} commit Whether to commit the change to Chrome. * @param {string} metric User metrics identifier. */ Preferences.clearPref = function(name, commit, metric) { if (!commit) { Preferences.getInstance().clearPrefNoCommit_(name); return; } var argumentList = [name]; if (metric != undefined) argumentList.push(metric); chrome.send('clearPref', argumentList); }; Preferences.prototype = { __proto__: cr.EventTarget.prototype, /** * Adds an event listener to the target. * @param {string} type The name of the event. * @param {!Function|{handleEvent:Function}} handler The handler for the * event. This is called when the event is dispatched. */ addEventListener: function(type, handler) { cr.EventTarget.prototype.addEventListener.call(this, type, handler); if (!(type in this.registeredPreferences_)) this.registeredPreferences_[type] = {}; }, /** * Initializes preference reading and change notifications. */ initialize: function() { var params1 = ['Preferences.prefsFetchedCallback']; var params2 = ['Preferences.prefsChangedCallback']; for (var prefName in this.registeredPreferences_) { params1.push(prefName); params2.push(prefName); } chrome.send('fetchPrefs', params1); chrome.send('observePrefs', params2); }, /** * Helper function for flattening of dictionary passed via fetchPrefs * callback. * @param {string} prefix Preference name prefix. * @param {object} dict Map with preference values. * @private */ flattenMapAndDispatchEvent_: function(prefix, dict) { for (var prefName in dict) { if (typeof dict[prefName] == 'object' && !this.registeredPreferences_[prefix + prefName]) { this.flattenMapAndDispatchEvent_(prefix + prefName + '.', dict[prefName]); } else { var event = new cr.Event(prefix + prefName); this.registeredPreferences_[prefix + prefName].orig = dict[prefName]; event.value = dict[prefName]; this.dispatchEvent(event); } } }, /** * Sets a preference and signals its new value. The change is propagated * throughout the UI code but is not committed to Chrome yet. The new value * and its data type are stored so that commitPref() can later be used to * invoke the appropriate set*Pref() method and actually commit the change. * @param {string} name Preference name. * @param {string} type Preference data type. * @param {*} value New preference value. * @private */ setPrefNoCommit_: function(name, type, value) { var pref = this.registeredPreferences_[name]; pref.action = 'set'; pref.type = type; pref.value = value; var event = new cr.Event(name); // Decorate pref value as CoreOptionsHandler::CreateValueForPref() does. event.value = { value: value, recommendedValue: pref.orig.recommendedValue, disabled: pref.orig.disabled, uncommitted: true, }; this.dispatchEvent(event); }, /** * Clears a preference and signals its new value. The change is propagated * throughout the UI code but is not committed to Chrome yet. * @param {string} name Preference name. * @private */ clearPrefNoCommit_: function(name) { var pref = this.registeredPreferences_[name]; pref.action = 'clear'; delete pref.type; delete pref.value; var event = new cr.Event(name); // Decorate pref value as CoreOptionsHandler::CreateValueForPref() does. event.value = { value: pref.orig.recommendedValue, controlledBy: 'recommended', recommendedValue: pref.orig.recommendedValue, disabled: pref.orig.disabled, uncommitted: true, }; this.dispatchEvent(event); }, /** * Commits a preference change to Chrome and signals the new preference * value. Does nothing if there is no uncommitted change. * @param {string} name Preference name. * @param {string} metric User metrics identifier. */ commitPref: function(name, metric) { var pref = this.registeredPreferences_[name]; switch (pref.action) { case 'set': switch (pref.type) { case 'bool': Preferences.setBooleanPref(name, pref.value, true, metric); break; case 'int': Preferences.setIntegerPref(name, pref.value, true, metric); break; case 'double': Preferences.setDoublePref(name, pref.value, true, metric); break; case 'string': Preferences.setStringPref(name, pref.value, true, metric); break; case 'url': Preferences.setURLPref(name, pref.value, true, metric); break; case 'list': Preferences.setListPref(name, pref.value, true, metric); break; } break; case 'clear': Preferences.clearPref(name, true, metric); break; } delete pref.action; delete pref.type; delete pref.value; }, /** * Rolls back a preference change and signals the original preference value. * Does nothing if there is no uncommitted change. * @param {string} name Preference name. */ rollbackPref: function(name) { var pref = this.registeredPreferences_[name]; if (!pref.action) return; delete pref.action; delete pref.type; delete pref.value; var event = new cr.Event(name); event.value = pref.orig; event.value.uncommitted = true; this.dispatchEvent(event); } }; /** * Callback for fetchPrefs method. * @param {object} dict Map of fetched property values. */ Preferences.prefsFetchedCallback = function(dict) { Preferences.getInstance().flattenMapAndDispatchEvent_('', dict); }; /** * Callback for observePrefs method. * @param {array} notification An array defining changed preference values. * notification[0] contains name of the change preference while its new value * is stored in notification[1]. */ Preferences.prefsChangedCallback = function(notification) { var event = new cr.Event(notification[0]); event.value = notification[1]; prefs = Preferences.getInstance(); prefs.registeredPreferences_[notification[0]] = {orig: notification[1]}; prefs.dispatchEvent(event); }; // Export return { Preferences: Preferences }; }); // Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. cr.define('options', function() { var BubbleBase = cr.ui.BubbleBase; var OptionsBubble = cr.ui.define('div'); OptionsBubble.prototype = { // Set up the prototype chain. __proto__: BubbleBase.prototype, /** * Initialization function for the cr.ui framework. */ decorate: function() { BubbleBase.prototype.decorate.call(this); this.classList.add('options-bubble'); }, /** * Set the DOM sibling node, i.e. the node as whose sibling the bubble * should join the DOM to ensure that focusable elements inside the bubble * follow the target element in the document's tab order. Only available * when the bubble is not being shown. * @param {HTMLElement} node The new DOM sibling node. */ set domSibling(node) { if (!this.hidden) return; this.domSibling_ = node; }, /** * Show the bubble. */ show: function() { if (!this.hidden) return; BubbleBase.prototype.show.call(this); this.domSibling_.showingBubble = true; var doc = this.ownerDocument; this.eventTracker_.add(doc, 'mousewheel', this, true); this.eventTracker_.add(doc, 'scroll', this, true); this.eventTracker_.add(doc, 'elementFocused', this, true); this.eventTracker_.add(window, 'resize', this); }, /** * Hide the bubble. */ hide: function() { BubbleBase.prototype.hide.call(this); this.domSibling_.showingBubble = false; }, /** * Handle events, closing the bubble when the user clicks or moves the focus * outside the bubble and its target element, scrolls the underlying * document or resizes the window. * @param {Event} event The event. */ handleEvent: function(event) { BubbleBase.prototype.handleEvent.call(this, event); switch (event.type) { // Close the bubble when the user clicks outside it, except if it is a // left-click on the bubble's target element (allowing the target to // handle the event and close the bubble itself). case 'mousedown': if (event.button == 0 && this.anchorNode_.contains(event.target)) break; // Close the bubble when the underlying document is scrolled. case 'mousewheel': case 'scroll': if (this.contains(event.target)) break; // Close the bubble when the window is resized. case 'resize': this.hide(); break; // Close the bubble when the focus moves to an element that is not the // bubble target and is not inside the bubble. case 'elementFocused': if (!this.anchorNode_.contains(event.target) && !this.contains(event.target)) { this.hide(); } break; } }, /** * Attach the bubble to the document's DOM, making it a sibling of the * |domSibling_| so that focusable elements inside the bubble follow the * target element in the document's tab order. * @private */ attachToDOM_: function() { var parent = this.domSibling_.parentNode; parent.insertBefore(this, this.domSibling_.nextSibling); }, }; return { OptionsBubble: OptionsBubble }; }); // Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. cr.define('options', function() { var Preferences = options.Preferences; /** * A controlled setting indicator that can be placed on a setting as an * indicator that the value is controlled by some external entity such as * policy or an extension. * @constructor * @extends {HTMLSpanElement} */ var ControlledSettingIndicator = cr.ui.define('span'); ControlledSettingIndicator.prototype = { __proto__: HTMLSpanElement.prototype, /** * Decorates the base element to show the proper icon. */ decorate: function() { var self = this; // If there is a pref, track its controlledBy and recommendedValue // properties in order to be able to bring up the correct bubble. if (this.pref) { Preferences.getInstance().addEventListener( this.pref, this.handlePrefChange.bind(this)); this.resetHandler = this.clearAssociatedPref_; } this.className = 'controlled-setting-indicator'; this.location = cr.ui.ArrowLocation.TOP_END; this.image = document.createElement('div'); this.image.tabIndex = 0; this.image.setAttribute('role', 'button'); this.image.addEventListener('click', this); this.image.addEventListener('keydown', this); this.image.addEventListener('mousedown', this); this.appendChild(this.image); }, /** * The given handler will be called when the user clicks on the 'reset to * recommended value' link shown in the indicator bubble. The |this| object * will be the indicator itself. * @param {function()} handler The handler to be called. */ set resetHandler(handler) { this.resetHandler_ = handler; }, /** * Whether the indicator is currently showing a bubble. * @type {boolean} */ get showingBubble() { return this.image.classList.contains('showing-bubble'); }, set showingBubble(showing) { this.image.classList.toggle('showing-bubble', showing); }, /** * Clears the preference associated with this indicator. * @private */ clearAssociatedPref_: function() { Preferences.clearPref(this.pref, !this.dialogPref); }, /* Handle changes to the associated pref by hiding any currently visible * bubble and updating the controlledBy property. * @param {Event} event Pref change event. */ handlePrefChange: function(event) { OptionsPage.hideBubble(); if (event.value.controlledBy) { this.controlledBy = !this.value || String(event.value.value) == this.value ? event.value.controlledBy : null; } else if (event.value.recommendedValue != undefined) { this.controlledBy = !this.value || String(event.value.recommendedValue) == this.value ? 'hasRecommendation' : null; } else { this.controlledBy = null; } }, /** * Handle mouse and keyboard events, allowing the user to open and close a * bubble with further information. * @param {Event} event Mouse or keyboard event. */ handleEvent: function(event) { switch (event.type) { // Toggle the bubble on left click. Let any other clicks propagate. case 'click': if (event.button != 0) return; break; // Toggle the bubble when or is pressed. Let any other // key presses propagate. case 'keydown': switch (event.keyCode) { case 13: // Return. case 32: // Space. break; default: return; } break; // Blur focus when a mouse button is pressed, matching the behavior of // other Web UI elements. case 'mousedown': if (document.activeElement) document.activeElement.blur(); event.preventDefault(); return; } this.toggleBubble_(); event.preventDefault(); event.stopPropagation(); }, /** * Open or close a bubble with further information about the pref. * @private */ toggleBubble_: function() { if (this.showingBubble) { OptionsPage.hideBubble(); } else { var self = this; // Construct the bubble text. if (this.hasAttribute('plural')) { var defaultStrings = { 'policy': loadTimeData.getString('controlledSettingsPolicy'), 'extension': loadTimeData.getString('controlledSettingsExtension'), }; } else { var defaultStrings = { 'policy': loadTimeData.getString('controlledSettingPolicy'), 'extension': loadTimeData.getString('controlledSettingExtension'), 'recommended': loadTimeData.getString('controlledSettingRecommended'), 'hasRecommendation': loadTimeData.getString('controlledSettingHasRecommendation'), }; } // No controller, no bubble. if (!this.controlledBy || !(this.controlledBy in defaultStrings)) return; var text = defaultStrings[this.controlledBy]; // Apply text overrides. if (this.hasAttribute('text' + this.controlledBy)) text = this.getAttribute('text' + this.controlledBy); // Create the DOM tree. var content = document.createElement('div'); content.className = 'controlled-setting-bubble-content'; content.setAttribute('controlled-by', this.controlledBy); content.textContent = text; if (this.controlledBy == 'hasRecommendation' && this.resetHandler_ && !this.readOnly) { var container = document.createElement('div'); var action = document.createElement('button'); action.classList.add('link-button'); action.classList.add('controlled-setting-bubble-action'); action.textContent = loadTimeData.getString('controlledSettingFollowRecommendation'); action.addEventListener('click', function(event) { self.resetHandler_(); }); container.appendChild(action); content.appendChild(container); } OptionsPage.showBubble(content, this.image, this, this.location); } }, }; /** * The name of the associated preference. * @type {string} */ cr.defineProperty(ControlledSettingIndicator, 'pref', cr.PropertyKind.ATTR); /** * Whether this indicator is part of a dialog. If so, changes made to the * associated preference take effect in the settings UI immediately but are * only actually committed when the user confirms the dialog. If the user * cancels the dialog instead, the changes are rolled back in the settings UI * and never committed. * @type {boolean} */ cr.defineProperty(ControlledSettingIndicator, 'dialogPref', cr.PropertyKind.BOOL_ATTR); /** * The value of the associated preference that the indicator represents. If * this is not set, the indicator will be visible whenever any value is * enforced or recommended. If it is set, the indicator will be visible only * when the enforced or recommended value matches the value it represents. * This allows multiple indicators to be created for a set of radio buttons, * ensuring that only one of them is visible at a time. */ cr.defineProperty(ControlledSettingIndicator, 'value', cr.PropertyKind.ATTR); /** * The status of the associated preference: * - 'policy': A specific value is enfoced by policy. * - 'extension': A specific value is enforced by an extension. * - 'recommended': A value is recommended by policy. The user could * override this recommendation but has not done so. * - 'hasRecommendation': A value is recommended by policy. The user has * overridden this recommendation. * - unset: The value is controlled by the user alone. * @type {string} */ cr.defineProperty(ControlledSettingIndicator, 'controlledBy', cr.PropertyKind.ATTR); // Export. return { ControlledSettingIndicator: ControlledSettingIndicator }; }); // Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. cr.define('options', function() { /** @const */ var List = cr.ui.List; /** @const */ var ListItem = cr.ui.ListItem; /** * Creates a deletable list item, which has a button that will trigger a call * to deleteItemAtIndex(index) in the list. */ var DeletableItem = cr.ui.define('li'); DeletableItem.prototype = { __proto__: ListItem.prototype, /** * The element subclasses should populate with content. * @type {HTMLElement} * @private */ contentElement_: null, /** * The close button element. * @type {HTMLElement} * @private */ closeButtonElement_: null, /** * Whether or not this item can be deleted. * @type {boolean} * @private */ deletable_: true, /** @override */ decorate: function() { ListItem.prototype.decorate.call(this); this.classList.add('deletable-item'); this.contentElement_ = this.ownerDocument.createElement('div'); this.appendChild(this.contentElement_); this.closeButtonElement_ = this.ownerDocument.createElement('button'); this.closeButtonElement_.className = 'raw-button row-delete-button custom-appearance'; this.closeButtonElement_.addEventListener('mousedown', this.handleMouseDownUpOnClose_); this.closeButtonElement_.addEventListener('mouseup', this.handleMouseDownUpOnClose_); this.closeButtonElement_.addEventListener('focus', this.handleFocus_.bind(this)); this.appendChild(this.closeButtonElement_); }, /** * Returns the element subclasses should add content to. * @return {HTMLElement} The element subclasses should popuplate. */ get contentElement() { return this.contentElement_; }, /** * Returns the close button element. * @return {HTMLElement} The close |